package com.framework.dubbo.producer.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.io.FileTypeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileReader;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.framework.dubbo.producer.file2html.FilePreviewFactory;
import com.framework.dubbo.producer.file2html.FileType;
import com.framework.dubbo.producer.service.Pdf2HtmlService;
import com.framework.dubbo.producer.config.Constants;
import com.framework.dubbo.producer.config.Pdf2HtmlProperties;
import com.framework.dubbo.producer.invoke.InvokeCommand;
import com.framework.dubbo.producer.command.CommandHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;

/**
 * 描述：
 * 服务工具类
 * @author xianggj
 * @Date 2021/8/25 9:32
 **/
@Slf4j
@Service
public class Pdf2HtmlServiceImpl implements Pdf2HtmlService {



    private static final String PDF = "pdf";
    private static final String HTML_SUFFIX = ".html";
    private static final String FAILED_PDF_DIR = "failed-pdfs";
    private static final String TASK_FILE = "log.txt";

    @Resource
    private Pdf2HtmlProperties pdf2HtmlProperties;

    @Autowired
    @Qualifier("apacheExec")
    private InvokeCommand invokeCommand;

    /**
     *  转换html失败的pdf写到这个目录, 方便后面手动转换排查原因
     */
    private static File failedPdfDir;

    @PostConstruct
    public void init() {
        failedPdfDir = FileUtil.mkdir(FileUtil.file(pdf2HtmlProperties.getWorkDir(), FAILED_PDF_DIR));
    }

    /**
     * 获取配置信息
     * @return
     */
    @Override
    public Pdf2HtmlProperties getPdf2HtmlProperties() {
        return pdf2HtmlProperties;
    }

    /**
     * 获取并发信息
     * @return
     */
    @Override
    public Map<String, Object> getMetric(){
        return invokeCommand.getMetric();
    }

    /**
     * 多文件转换
     * @param files
     */
    @Override
    public File pdf2html(MultipartFile[] files) throws Exception{
        //获取目标文件夹
        File dir = FileUtil.mkdir(FileUtil.file(pdf2HtmlProperties.getWorkDir(), IdUtil.simpleUUID()));

        List<File> fileList = new ArrayList<>(files.length);
        for (MultipartFile f : files) {
            if (f == null || f.isEmpty()) {
                continue;
            }
            // 写入本地工作目录
            File localFile = FileUtil.writeFromStream(f.getInputStream(), FileUtil.file(dir, f.getOriginalFilename()));
            // 只处理pdf文件
            if (isPdf(localFile)) {
                fileList.add(localFile);
            }
        }

        if (CollUtil.isEmpty(fileList)) {
            FileUtil.appendString("错误：上传的文件中没有pdf文件", FileUtil.file(pdf2HtmlProperties.getWorkDir(), TASK_FILE), CharsetUtil.CHARSET_UTF_8);
            return dir;
        }
        //记时
        TimeInterval timer = DateUtil.timer();
        int size = fileList.size();
        CountDownLatch latch = new CountDownLatch(size);
        // 处理失败的pdf统计
        Map<String, Throwable> failedMap = new ConcurrentHashMap<>(10);
        String distDir = dir.getAbsolutePath();
        for (File file : fileList) {
            String htmlFileName = StrUtil.subBefore(file.getName(), ".", true)+ HTML_SUFFIX;
            String filePath = file.getAbsolutePath();
            String thisDistDir = distDir+ File.separator +
                    StrUtil.subBefore(htmlFileName, ".", false);
            String command = CommandHandler.getInstance()
                    .assembleCommand(pdf2HtmlProperties, filePath, thisDistDir, htmlFileName);
            // 异步调用pdf2htmlEX命令行工具
            try {
                failedMap.putAll(invokeCommand.invokeCommand(file, command, latch));
            } catch (Throwable throwable) {
                throwable.printStackTrace();
                latch.countDown();
            }
        }

        // 等待所有子进程结束
        latch.await();

        log.info("pdf2html一共耗时{}ms, pdf数量为{}", timer.interval(), size);

        // 记录 统计数据写入文件log.txt, 转换html失败的pdf写入固定目录
        recordTaskResult(size, failedMap, dir, fileList);
        return dir;
    }


    /**
     * 单文件转换
     * @param file
     * @param isFull 默认为false 为true表示图片安装base64处理
     */
    @Override
    public String pdf2html(MultipartFile file, boolean isFull) throws Exception{

        //计时器
        TimeInterval timer = DateUtil.timer();
        //避免文件名称重复 唯一id
        String id = IdUtil.simpleUUID();
        //获取目标文件夹
        File dir = FileUtil.mkdir(FileUtil.file(pdf2HtmlProperties.getWorkDir(), id));
        //源文件名去除空格
        String originalFilename = StrUtil.removeAll(file.getOriginalFilename(), " ");
        String ext = StrUtil.subAfter(originalFilename, ".", true);
        // 写入本地工作目录 固定写成index.pdf 避免不必要的麻烦
        File localFile = FileUtil.writeFromStream(file.getInputStream(), FileUtil.file(dir, "index"+ Constants.DOT + ext));
        String localFileName = StrUtil.subBefore(localFile.getName(), ".", true);
        // 只处理pdf文件
        if (!isPdf(localFile)) {
            String serviceUrl = Constants.getHttp(pdf2HtmlProperties.getServiceHost(), getPdf2HtmlProperties().getServicePort())
                    + pdf2HtmlProperties.getServiceUrl();
            FileType fileType = FileType.enumOf(originalFilename);
            //转成其他文件的处理
            String htmlContext = FilePreviewFactory
                    .getToolByType(fileType)
                    .setStaticUrl(serviceUrl)
                    .setStaticPath(pdf2HtmlProperties.getSourceDir())
                    .setOldFileName(originalFilename)
                    .convert2Html(localFile, fileType.getValue());
            log.info("文件[{}]转换一共耗时{}ms", originalFilename, timer.interval());
            return htmlContext;
        }
        String htmlFileName = localFileName + HTML_SUFFIX;
        String distDir = dir.getAbsolutePath();
        String filePath = localFile.getAbsolutePath();
        String command = null;
        if (isFull){
            command = getFullCommand(filePath, htmlFileName, distDir);
        } else {
            command = CommandHandler.getInstance()
                    .assembleCommand(pdf2HtmlProperties, filePath, distDir, htmlFileName);
        }
        String context = null;
        // 异步调用pdf2htmlEX命令行工具
        try {
            //异步执行  必须等待
            CountDownLatch latch = new CountDownLatch(1);
            Map<String, Throwable> throwableMap =
                    invokeCommand.invokeCommand(localFile, command, latch);
            if (throwableMap.size() > 0 ){
                latch.countDown();
                throw new RuntimeException("pdf转换html出错了");
            }
            // 等待所有子进程结束
            latch.await();
            String serviceUrl = Constants.getHttp(pdf2HtmlProperties.getServiceHost(), getPdf2HtmlProperties().getServicePort())
                    + pdf2HtmlProperties.getServiceUrl() + id + Constants.SEPARATOR;
            //获取html文件内容
            FileReader fileReader = new FileReader(distDir + File.separator + htmlFileName);
            String result = fileReader.readString();
            //路径转换
            result = StrUtil.replace(result, "src=\"", "src=\""+serviceUrl);
            context = StrUtil.replace(result, " href=\"", " href=\"" +serviceUrl);
            //静态文件copy
            FileUtil.copy(distDir, pdf2HtmlProperties.getSourceDir(), true);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            throw new RuntimeException("系统异常："+ throwable.getMessage());
        } finally {
            FileUtil.del(dir);
        }
        log.info("文件[{}]转换一共耗时{}ms", originalFilename, timer.interval());
        return context;
    }

    /**
     * 获取命令 <p>不分解图片文件</p>
     * @param filePath
     * @param htmlFileName
     * @param distDir
     * @return
     */
    private String getFullCommand(String filePath, String htmlFileName, String distDir) {
        String command = CommandHandler.getInstance()
                .assembleCommand(pdf2HtmlProperties, filePath, distDir, htmlFileName);
        command = StrUtil.subBefore(command, "woff", true)+ "woff";
        return command;
    }


    /**
     * 是否是pdf文件
     * @param file
     * @return
     */
    public static boolean isPdf(File file) {
        try {
            String localFileExt = StrUtil.subAfter(file.getName(), ".", true);
            return PDF.equalsIgnoreCase(FileTypeUtil.getType(file)) && PDF.equalsIgnoreCase(localFileExt);
        } catch (Exception e) {
            log.error("识别pdf类型失败, 文件名:{}, error: {}", file.getAbsolutePath(), e);
            return false;
        }
    }


    /**
     * 转换的结果生成文件
     *  将错误文件记录下来，方便以后查看
     * @param total 总共执行次数
     * @param failedMap 失败文件名列表
     * @param workDir 当前工作目录
     * @param pdfs 源文件 （包含成功文件和失败文件）
     */
    public static void recordTaskResult(int total, Map<String, Throwable> failedMap, File workDir, List<File> pdfs) {
        List<String> list = new ArrayList<>();
        list.add("total:" + total);
        list.add("success:" + (total - failedMap.size()));
        list.add("failed:" + failedMap.size());

        list.add("");
        list.add("failed-pdfs:");
        list.add("");

        Set<String> failedNames = failedMap.keySet();
        list.addAll(failedNames);

        // 记录任务完成大致情况
        FileUtil.writeLines(list, FileUtil.file(workDir, TASK_FILE), CharsetUtil.CHARSET_UTF_8);

        // 转换失败的pdf写入其他目录,后续可能需要进一步处理
        if (CollUtil.isNotEmpty(failedNames)) {
            for (File pdf : pdfs) {
                String name = pdf.getName();
                if (failedNames.contains(name)) {
                    File dest = FileUtil.file(failedPdfDir, name);
                    if (dest.exists()) {
                        dest = FileUtil.file(failedPdfDir, IdUtil.simpleUUID() + "-" + name);
                    }
                    FileUtil.copyFile(pdf, dest);
                }
            }
        }
    }

}
